/*
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package botobjects;

import database.DBConnectionFactory;
import interfaces.BotObject;
import main.CommonMethods;
import main.Constants;
import main.Main;
import org.apache.log4j.Logger;
import org.jibble.pircbot.Colors;
import tools.StringTools;
import tools.UtopiaTime;
import utopiaintelpackage.database.SoMtoDB;
import utopiaintelpackage.database.SoStoDB;
import utopiaintelpackage.database.SoTtoDB;
import utopiaintelpackage.database.SurveytoDB;
import utopiaintelpackage.som.SoM;
import utopiaintelpackage.sos.SoS;
import utopiaintelpackage.sot.SoT;
import utopiaintelpackage.survey.Survey;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.*;

public class Province implements BotObject {
    /**
     * Removes the specified province
     *
     * @param args Specifies the name of the province to be removed
     * @return true if the province is successfully removed
     */
    public static boolean deleteFromDB(final String... args) {
        boolean out = false;
        if (args.length < 1)
            return out;
        PreparedStatement ps = null;
        try {
            ps = DBConnectionFactory.getConnectionHandler().getConnection().prepareStatement("DELETE FROM province WHERE name = ?");
            ps.setString(1, args[0]);
            if (ps.executeUpdate() == 1) {
                out = true;
            }
        } catch (final SQLException e) {
            Logger.getLogger("Error").error("", e);
        } finally {
            try {
                ps.close();
            } catch (final Exception e) {
            }
        }
        return out;
    }

    /**
     * Returns the specified intra kd province. Uses fuzzy matching and levenshtein distance to find the closest match
     *
     * @param searchFor
     * @return a Province object. Returns null if no province is found
     */
    public static Province getInternalProvince(final String searchFor) {
        if (searchFor == null)
            return null;
        Province out = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = DBConnectionFactory
                    .getConnectionHandler()
                    .getConnection()
                    .prepareStatement(
                            "SELECT * FROM " + "kdprovince,province WHERE mainnick LIKE ? AND " + "kdprovince.province = province.name");
            ps.setString(1, "%" + searchFor + "%");
            rs = ps.executeQuery();

            String name = "";
            String provname = "";
            String race = "";
            String pers = "";
            int land = 0;
            int nw = 0;
            while (rs.next()) {
                if ("".equals(name)) {
                    name = rs.getString("mainnick");
                    provname = rs.getString("province");
                    race = rs.getString("race");
                    pers = rs.getString("personality");
                    land = rs.getInt("land");
                    nw = rs.getInt("nw");
                } else {
                    if (StringTools.getLevenshteinDistance(searchFor, rs.getString("mainnick")) < StringTools.getLevenshteinDistance(
                            searchFor, name)) {
                        name = rs.getString("mainnick");
                        provname = rs.getString("province");
                        race = rs.getString("race");
                        pers = rs.getString("personality");
                    }
                }
            }

            try {
                ps.close();
            } catch (final Exception e) {
            }

            ps = DBConnectionFactory
                    .getConnectionHandler()
                    .getConnection()
                    .prepareStatement(
                            "SELECT * FROM " + "kdprovince,province WHERE province LIKE ? AND " + "kdprovince.province = province.name");
            ps.setString(1, "%" + searchFor + "%");
            rs = ps.executeQuery();

            while (rs.next()) {
                if ("".equals(name)) {
                    name = rs.getString("mainnick");
                    provname = rs.getString("province");
                    race = rs.getString("race");
                    pers = rs.getString("personality");
                    land = rs.getInt("land");
                    nw = rs.getInt("nw");
                } else {
                    if (StringTools.getLevenshteinDistance(searchFor, rs.getString("province")) < StringTools.getLevenshteinDistance(
                            searchFor, name)) {
                        name = rs.getString("mainnick");
                        provname = rs.getString("province");
                        race = rs.getString("race");
                        pers = rs.getString("personality");
                    }
                }
            }

            if (!"".equals(name)) {
                out = new Province(name, provname, "(" + Main.getBotSettings().getKdLoc() + ")", race, pers, "", land, nw);
            }
        } catch (final SQLException e) {
            Logger.getLogger("Error").error("", e);
        } finally {
            try {
                rs.close();
            } catch (final Exception e) {
            }
            try {
                ps.close();
            } catch (final Exception e) {
            }
        }
        return out;
    }

    /**
     * Returns a list of intra kd provinces
     *
     * @return a List of Province objects
     */
    public static List<Province> getInternalProvinces() {
        List<Province> out = new LinkedList<Province>();
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = DBConnectionFactory.getConnectionHandler().getConnection()
                    .prepareStatement("SELECT * FROM " + "province,kdprovince WHERE province.name = kdprovince.province");
            rs = ps.executeQuery();

            while (rs.next()) {
                out.add(new Province(rs.getString("mainnick"), rs.getString("name"), "(" + Main.getBotSettings().getKdLoc() + ")", rs
                        .getString("race"), rs.getString("personality"), rs.getString("title"), rs.getInt("land"), rs.getInt("nw")));
            }
        } catch (final SQLException e) {
            Logger.getLogger("Error").error("", e);
        } finally {
            try {
                rs.close();
            } catch (final Exception e) {
            }
            try {
                ps.close();
            } catch (final Exception e) {
            }
        }
        return out;
    }

    /**
     * Returns the specified province
     *
     * @param searchFor
     * @return a Province object. Returns null if no province is found
     */
    public static Province getProvince(final String searchFor) {
        if (searchFor == null)
            return null;
        Province out = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = DBConnectionFactory.getConnectionHandler().getConnection()
                    .prepareStatement("SELECT * FROM province WHERE name = ?");
            ps.setString(1, searchFor);
            rs = ps.executeQuery();

            if (rs.next()) {
                out = new Province(rs.getString("name"), rs.getString("kdloc"), rs.getString("race"), rs.getString("personality"),
                        rs.getString("title"), rs.getInt("land"), rs.getInt("nw"));
            }

            try {
                ps.close();
            } catch (final Exception e) {
            }

            ps = DBConnectionFactory.getConnectionHandler().getConnection()
                    .prepareStatement("SELECT * FROM kdprovince WHERE province = ?");
            ps.setString(1, searchFor);
            rs = ps.executeQuery();

            if (rs.next() && out != null) {
                out.setOwner(rs.getString("mainnick"));
            }
        } catch (final SQLException e) {
            Logger.getLogger("Error").error("", e);
        } finally {
            try {
                rs.close();
            } catch (final Exception e) {
            }
            try {
                ps.close();
            } catch (final Exception e) {
            }
        }
        return out;
    }

    /**
     * Returns a list of provinces from the specified kd
     *
     * @return a List of Province objects
     */
    public static List<Province> getProvinces(final String kd) {
        List<Province> out = new LinkedList<Province>();
        if (kd == null)
            return out;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = DBConnectionFactory.getConnectionHandler().getConnection()
                    .prepareStatement("SELECT * FROM province WHERE province.kdloc = ?");
            ps.setString(1, kd);
            rs = ps.executeQuery();

            while (rs.next()) {
                out.add(new Province(rs.getString("name"), "(" + Main.getBotSettings().getKdLoc() + ")", rs.getString("race"), rs
                        .getString("personality"), rs.getString("title"), rs.getInt("land"), rs.getInt("nw")));
            }
        } catch (final SQLException e) {
            Logger.getLogger("Error").error("", e);
        } finally {
            try {
                rs.close();
            } catch (final Exception e) {
            }
            try {
                ps.close();
            } catch (final Exception e) {
            }
        }
        return out;
    }

    /**
     * Updates the amount of thieves the province has
     *
     * @param name
     * @param thieves
     * @return
     */
    public static boolean updateThieves(final String name, final int thieves) {
        PreparedStatement ps = null;
        int out = 0;
        try {
            ps = DBConnectionFactory.getConnectionHandler().getConnection()
                    .prepareStatement("UPDATE province SET " + "thieves = ?, infiltrated = true, infiltratedtime = ? WHERE name = ?");
            ps.setInt(1, thieves);
            ps.setTimestamp(2, new Timestamp(GregorianCalendar.getInstance().getTimeInMillis()));
            ps.setString(3, name);
            out = ps.executeUpdate();
        } catch (final SQLException e) {
            Logger.getLogger("Error").error("", e);
        } finally {
            try {
                ps.close();
            } catch (final Exception e) {
            }
        }
        return out == 1;
    }

    /**
     * Updates the amount of wizards the province has
     *
     * @param name
     * @param wizards
     * @return
     */
    public static boolean updateWizards(final String name, final int wizards) {
        PreparedStatement ps = null;
        int out = 0;
        try {
            ps = DBConnectionFactory.getConnectionHandler().getConnection()
                    .prepareStatement("UPDATE sot SET " + "wizards = ? WHERE name = ?");
            ps.setInt(1, wizards);
            ps.setString(2, name);
            out = ps.executeUpdate();
        } catch (final SQLException e) {
            Logger.getLogger("Error").error("", e);
        } finally {
            try {
                ps.close();
            } catch (final Exception e) {
            }
        }
        return out == 1;
    }

    private String owner = "";

    private String name = "";

    private String kd = "";

    private String race = "";

    private String personality = "";

    private String title = "";

    private int land = 0;

    private int nw = 0;

    public Province(final String name, final String kd, final String race, final String personality, final String title,
                    final int land, final int nw) {
        this.name = name;
        this.kd = kd;
        this.race = race;
        this.personality = personality;
        this.title = title;
        this.land = land;
        this.nw = nw;
    }

    public Province(final String owner, final String name, final String kd, final String race, final String personality,
                    final String title, final int land, final int nw) {
        this(name, kd, race, personality, title, land, nw);
        this.owner = owner;
    }

    public List<String> getIRCOutput(final int outputType, final String... args) {
        List<String> out = new LinkedList<String>();
        if (outputType == Constants.PROVINCE_GET_OWNER && getOwner() != null && isComplete()) {
            SoT sot = getSot();
            String info = "";
            if (sot != null) {
                info = " [" + Colors.TEAL + sot.getLand() + Colors.NORMAL + "land, " + Colors.RED + sot.getNw() + Colors.NORMAL
                        + "nw]" + " Last updated: " + UtopiaTime.compareTimes(sot.getUpdated().getTime()) + " ago";
            }
            out.add(getOwner() + " = " + getName() + " (" + Colors.TEAL + getRace() + Colors.NORMAL + "/" + Colors.RED
                    + getPersonality() + Colors.NORMAL + ")" + info);
        } else if (outputType == Constants.PROVINCE_GET_SOT || outputType == Constants.PROVINCE_GET_SHORT_SOT) {
            SoT sot = getSot();
            if (sot != null) {
                out.addAll(sot.getIRCOutput(outputType == Constants.PROVINCE_GET_SHORT_SOT));
            }
        } else if (outputType == Constants.PROVINCE_GET_SURVEY || outputType == Constants.PROVINCE_GET_SHORT_SURVEY) {
            Survey survey = getSurvey();
            if (survey != null) {
                out.addAll(survey.getIRCOutput(outputType == Constants.PROVINCE_GET_SHORT_SURVEY));
            }
        } else if (outputType == Constants.PROVINCE_GET_SOS || outputType == Constants.PROVINCE_GET_SHORT_SOS) {
            SoS sos = getSos();
            if (sos != null) {
                out.addAll(sos.getIRCOutput(outputType == Constants.PROVINCE_GET_SHORT_SOS));
            }
        } else if (outputType == Constants.PROVINCE_GET_SOM || outputType == Constants.PROVINCE_GET_SHORT_SOM) {
            SoM som = getSom();
            if (som != null) {
                out.addAll(som.getIRCOutput(outputType == Constants.PROVINCE_GET_SHORT_SOM));
            }
        }
        return out;
    }

    public String getKd() {
        return kd;
    }

    public int getLand() {
        return land;
    }

    public String getName() {
        return name;
    }

    public int getNw() {
        return nw;
    }

    public String getOwner() {
        return owner;
    }

    public String getPersonality() {
        return personality;
    }

    public String getRace() {
        return race;
    }

    public Map<String, Object> getRPCOutput(final int... includes) {
        Map<String, Object> out = new HashMap<String, Object>();
        if (!isComplete())
            return out;
        if (!"".equals(owner)) {
            out.put("Owner", getOwner());
        }
        out.put("Name", getName());
        out.put("KD", getKd());
        out.put("Race", getRace());
        out.put("Personality", getPersonality());
        if (!"".equals(getTitle())) {
            out.put("Title", getTitle());
        }
        out.put("Land", getLand());
        out.put("NW", getNw());
        List<Integer> inc = new LinkedList<Integer>();
        for (int i : includes) {
            inc.add(i);
        }
        if (inc.contains(Constants.PROVINCE_INCLUDE_SOT) || inc.contains(Constants.PROVINCE_INCLUDE_ALL)) {
            SoT sot = getSot();
            if (sot != null) {
                out.put("SoT", sot.getRPCOutput());
            }
        }
        if (inc.contains(Constants.PROVINCE_INCLUDE_SOS) || inc.contains(Constants.PROVINCE_INCLUDE_ALL)) {
            SoS sos = getSos();
            if (sos != null) {
                out.put("SoS", sos.getRPCOutput());
            }
        }
        if (inc.contains(Constants.PROVINCE_INCLUDE_SOM) || inc.contains(Constants.PROVINCE_INCLUDE_ALL)) {
            SoM som = getSom();
            if (som != null) {
                out.put("SoM", som.getRPCOutput());
            }
        }
        if (inc.contains(Constants.PROVINCE_INCLUDE_SURVEY) || inc.contains(Constants.PROVINCE_INCLUDE_ALL)) {
            Survey survey = getSurvey();
            if (survey != null) {
                out.put("Survey", survey.getRPCOutput());
            }
        }
        return out;
    }

    /**
     * Returns a SoM for this province
     *
     * @returna a SoM. Returns null if no SoM is found
     */
    public SoM getSom() {
        return SoMtoDB.getSoM(getName());
    }

    /**
     * Returns a SoS for this province
     *
     * @returna a SoS. Returns null if no SoS is found
     */
    public SoS getSos() {
        return SoStoDB.getSoS(getName());
    }

    /**
     * Returns a SoT for this province
     *
     * @returna a SoT. Returns null if no SoT is found
     */
    public SoT getSot() {
        return SoTtoDB.getSoT(getName());
    }

    /**
     * Returns a Survey for this province
     *
     * @returna a Survey. Returns null if no Survey is found
     */
    public Survey getSurvey() {
        return SurveytoDB.getSurvey(getName());
    }

    public String getTitle() {
        return title;
    }

    /**
     * Checks if the province has all the necessary info
     *
     * @return
     */
    public boolean isComplete() {
        return !StringTools.isNullOrEmpty(getName()) && getKd() != null && getRace() != null && getPersonality() != null
                && getTitle() != null && getLand() >= 0;
    }

    public boolean saveToDB() {
        boolean out = false;
        if (!isComplete())
            return out;
        if (!StringTools.isNullOrEmpty(getOwner())) { // an owner means it's an intra kd province to be added
            PreparedStatement ps = null;
            PreparedStatement ps2 = null;
            ResultSet rs = null;
            try {
                ps = DBConnectionFactory.getConnectionHandler().getConnection()
                        .prepareStatement("SELECT * FROM kdprovince WHERE mainnick = ?");
                ps.setString(1, getOwner());
                rs = ps.executeQuery();

                if (rs.next()) {
                    ps2 = DBConnectionFactory.getConnectionHandler().getConnection()
                            .prepareStatement("DELETE FROM province WHERE name = ?");
                    ps2.setString(1, rs.getString("province"));
                    ps2.executeUpdate();
                }

                try {
                    ps.close();
                } catch (final Exception e) {
                }

                ps = DBConnectionFactory.getConnectionHandler().getConnection()
                        .prepareStatement("DELETE FROM province WHERE name = ?");
                ps.setString(1, getName());
                ps.executeUpdate();

                try {
                    ps.close();
                } catch (final Exception e) {
                }

                ps = DBConnectionFactory.getConnectionHandler().getConnection()
                        .prepareStatement("INSERT INTO " + "province VALUES(?,?,?,?,?,?,?,?,?,false,?)");
                ps.setString(1, getName());
                ps.setString(2, getRace());
                ps.setString(3, "");
                ps.setString(4, getPersonality());
                ps.setString(5, "");
                ps.setString(6, getKd());
                ps.setInt(7, 0);
                ps.setInt(8, 0);
                ps.setInt(9, 0);
                ps.setTimestamp(10, new Timestamp(GregorianCalendar.getInstance().getTimeInMillis()));
                ps.executeUpdate();

                try {
                    ps.close();
                } catch (final Exception e) {
                }

                ps = DBConnectionFactory.getConnectionHandler().getConnection()
                        .prepareStatement("INSERT INTO kdprovince VALUES(?,?)");
                ps.setString(1, getOwner());
                ps.setString(2, getName());

                if (ps.executeUpdate() == 1) {
                    out = true;
                }
            } catch (final SQLException e) {
                Logger.getLogger("Error").error("", e);
            } finally {
                try {
                    rs.close();
                } catch (final Exception e) {
                }
                try {
                    ps.close();
                } catch (final Exception e) {
                }
                try {
                    ps2.close();
                } catch (final Exception e) {
                }
            }
            return out;
        } else { // no owner means we want to add or update an existing province
            PreparedStatement ps = null;
            try {
                if (!"".equals(CommonMethods.provinceExists(getName()))) {
                    ps = DBConnectionFactory
                            .getConnectionHandler()
                            .getConnection()
                            .prepareStatement("UPDATE province " + "SET race = ?, title = ?, kdloc = ?, land = ?, nw = ? WHERE name = ?");
                    ps.setString(1, getRace());
                    ps.setString(2, getTitle());
                    ps.setString(3, getKd());
                    ps.setInt(4, getLand());
                    ps.setInt(5, getNw());
                    ps.setString(6, getName());
                    if (ps.executeUpdate() == 1) {
                        out = true;
                    }
                } else {
                    ps = DBConnectionFactory.getConnectionHandler().getConnection()
                            .prepareStatement("INSERT INTO " + "province VALUES (?,?,?,?,?,?,?,?,0,false,?)");
                    ps.setString(1, getName());
                    ps.setString(2, getRace());
                    ps.setString(3, "Unknown");
                    ps.setString(4, "Unknown");
                    ps.setString(5, getTitle());
                    ps.setString(6, getKd());
                    ps.setInt(7, getLand());
                    ps.setInt(8, getNw());
                    ps.setTimestamp(9, new Timestamp(GregorianCalendar.getInstance().getTimeInMillis()));
                    if (ps.executeUpdate() == 1) {
                        out = true;
                    }
                }
            } catch (final SQLException e) {
                Logger.getLogger("Error").error("", e);
            } finally {
                try {
                    ps.close();
                } catch (final Exception e) {
                }
            }
        }
        return out;
    }

    public void setOwner(final String owner) {
        this.owner = owner;
    }
}
